package main

import (
	"bytes"
	"encoding/gob"
	"log"
	"crypto/sha256"
	"crypto/ecdsa"
	"encoding/hex"
	"crypto/rand"
	"fmt"
	"strings"
	"crypto/elliptic"
	"math/big"
)

const subsidy = 10
type Transaction struct {
	ID	[]byte
	Vin	[]TXInput
	Vout	[]TXOutput
}
type TXInput struct {
	Txid	[]byte
	Vout	int
	Signature	[]byte
	PubKey	[]byte
}
// UsesKey checks whether the address initiated the transaction
func (in *TXInput)	UsesKey(pubKeyHash	[]byte) bool{
	lockingHash	:= HashPubKey(in.PubKey)
	return bytes.Compare(lockingHash,pubKeyHash)==0
}
type TXOutput struct {
	Value	int
	PubKeyHash	[]byte
}
// signs the output
func (out *TXOutput)	Lock(address []byte){
	pubKeyHash	:= Base58Decode(address)
	pubKeyHash = pubKeyHash[1:len(pubKeyHash)-4]
	out.PubKeyHash = pubKeyHash
}
// checks if the output can be used by the owner of the pubkey
func (out *TXOutput) IsLockedWithKey(pubKeyHash	 []byte) bool{
	return bytes.Compare(out.PubKeyHash,pubKeyHash)==0
}
func NewTXOutput(value int,address string) *TXOutput{
	txo := &TXOutput{value,nil}
	txo.Lock([]byte(address))
	return txo
}
// checks whether the transaction is coinbase
func (tx Transaction) IsCoinbase() bool{
	return len(tx.Vin)==1&&len(tx.Vin[0].Txid)==0&&tx.Vin[0].Vout==-1
}
func (tx Transaction) Serialize() []byte{
	var encoded bytes.Buffer
	enc := gob.NewEncoder(&encoded)
	err := enc.Encode(tx)
	if err != nil {
		log.Panic(err)
	}
	return encoded.Bytes()
}
// returns the hash of the Transaction
func (tx *Transaction) Hash() []byte{
	var hash [32]byte
	txCopy := *tx
	txCopy.ID = []byte{}
	hash = sha256.Sum256(txCopy.Serialize())
	return hash[:]
}
// signs each input of a Transaction
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey,prevTXs map[string]Transaction){
	if tx.IsCoinbase(){
		return
	}
	for _,vin:= range tx.Vin{
		if prevTXs[hex.EncodeToString(vin.Txid)].ID==nil{
			log.Panic("ERROR: Previous transaction is not correct")
		}
	}
	txCopy := tx.TrimmedCopy()
	for inID,vin := range txCopy.Vin{
		prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
		txCopy.Vin[inID].Signature = nil
		txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
		txCopy.ID = txCopy.Hash()
		txCopy.Vin[inID].PubKey = nil
		r,s,err := ecdsa.Sign(rand.Reader,&privKey,txCopy.ID)
		if err != nil {
			log.Panic(err)
		}
		signature := append(r.Bytes(),s.Bytes()...)
		tx.Vin[inID].Signature = signature
	}
}
// creates a trimmed copy of Transaction to be used in signing
func (tx *Transaction) TrimmedCopy() Transaction{
	var inputs []TXInput
	var outputs []TXOutput
	for _,vin := range tx.Vin{
		inputs = append(inputs,TXInput{vin.Txid,vin.Vout,nil,nil})
	}
	for _,vout := range tx.Vout{
		outputs = append(outputs,TXOutput{vout.Value,vout.PubKeyHash})
	}
	txCopy := Transaction{tx.ID,inputs,outputs}
	return txCopy
}
// return a human-readable representation of a transaction
func (tx Transaction) String() string{
	var lines []string
	lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.ID))
	for i,input := range tx.Vin{
		lines = append(lines, fmt.Sprintf("     Input %d:", i))
		lines = append(lines, fmt.Sprintf("       TXID:      %x", input.Txid))
		lines = append(lines, fmt.Sprintf("       Out:       %d", input.Vout))
		lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
		lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PubKey))
	}
	for i,output := range tx.Vout{
		lines = append(lines, fmt.Sprintf("     Output %d:", i))
		lines = append(lines, fmt.Sprintf("       Value:  %d", output.Value))
		lines = append(lines, fmt.Sprintf("       Script: %x", output.PubKeyHash))
	}
	return strings.Join(lines,"\n")
}
// verifies signatures of Transaction inputs
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool{
	if tx.IsCoinbase(){
		return true
	}
	for _,vin := range tx.Vin{
		if prevTXs[hex.EncodeToString(vin.Txid)].ID==nil{
			log.Panic("ERROR: Previous transaction is not correct")
		}
	}
	txCopy := tx.TrimmedCopy()
	curve := elliptic.P256()
	for inID,vin := range tx.Vin{
		prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
		txCopy.Vin[inID].Signature = nil
		txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
		txCopy.ID = txCopy.Hash()
		txCopy.Vin[inID].PubKey = nil
		r := big.Int{}
		s := big.Int{}
		sigLen := len(vin.Signature)
		r.SetBytes(vin.Signature[:(sigLen/2)])
		s.SetBytes(vin.Signature[(sigLen/2):])
		x := big.Int{}
		y := big.Int{}
		keyLen := len(vin.PubKey)
		x.SetBytes(vin.PubKey[:(keyLen/2)])
		y.SetBytes(vin.PubKey[(keyLen/2):])
		rawPubKey := ecdsa.PublicKey{curve,&x,&y}
		if ecdsa.Verify(&rawPubKey,txCopy.ID,&r,&s)==false{
			return false
		}
	}
	return true
}
func NewCoinbaseTX(to,data string) *Transaction{
	if data == ""{
		data = fmt.Sprintf("Reward to '%s'", to)
	}
	txin := TXInput{[]byte{},-1,nil,[]byte(data)}
	txout := NewTXOutput(subsidy,to)
	tx := Transaction{nil,[]TXInput{txin},[]TXOutput{*txout}}
	tx.ID = tx.Hash()
	return &tx
}

// creates a new transaction
func NewUTXOTransaction(from,to string,amount int,blockchain *Blockchain) *Transaction {
	var inputs []TXInput
	var outputs []TXOutput
	wallets,err := NewWallets()
	if err != nil {
		log.Panic(err)
	}
	wallet := wallets.GetWallet(from)
	pubKeyHash := HashPubKey(wallet.PublicKey)
	acc ,validOutputs := blockchain.FindSpendableOutputs(pubKeyHash,amount)
	if acc < amount{
		log.Panic("ERROR:Not enough funds")
	}
	// build a list of inputs
	for txid,outs := range validOutputs{
		txID,err := hex.DecodeString(txid)
		if err != nil {
			log.Panic(err)
		}
		for _,out := range outs{
			input := TXInput{txID,out,nil,wallet.PublicKey}
			inputs = append(inputs,input)
		}
	}
	// build a list of outputs
	outputs = append(outputs,*NewTXOutput(amount,to))
	if acc > amount{
		outputs = append(outputs,*NewTXOutput(acc-amount,from))
	}
	tx := Transaction{nil,inputs,outputs}
	tx.ID = tx.Hash()
	blockchain.SignTransaction(&tx,wallet.PrivateKey)
	return &tx
}